package edu.pucsp.predatorprey.simulador.java.terreno.impl;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;

import edu.pucsp.predatorprey.simulador.java.criatura.AbstractCriatura;
import edu.pucsp.predatorprey.simulador.java.posicao.impl.Direcao;
import edu.pucsp.predatorprey.simulador.java.posicao.impl.Posicao;
import edu.pucsp.predatorprey.simulador.java.terreno.api.ObservadorTerreno;
import edu.pucsp.predatorprey.simulador.java.terreno.api.Terreno;

/**
 * @author José Almada
 */
public class TerrenoImpl implements Terreno {

	public static final int HEIGHT = 100;

	public static final int WIDTH = 100;

	private final List<ObservadorTerreno> observadores = new LinkedList<ObservadorTerreno>();

	private final Map<AbstractCriatura, Posicao> posicaoPresas = new ConcurrentHashMap<AbstractCriatura, Posicao>();

	private final Map<AbstractCriatura, Posicao> posicaoPredadores = new ConcurrentHashMap<AbstractCriatura, Posicao>();

	private final AbstractCriatura[][] terreno = new AbstractCriatura[HEIGHT][WIDTH];

	private final ControleVida controleVida = new ControleVida();

	public TerrenoImpl(Set<AbstractCriatura> criaturas) {
		this.disporCriaturasAleatoriamente(criaturas);
	}

	@Override
	public synchronized void moverCriatura(AbstractCriatura criatura, Direcao direcao) {
		Posicao posicao = this.getPosicao(criatura);

		if (posicao != null) { // A criatura pode ter morrido antes de chegar
								// sua vez de se movimentar
			List<Posicao> posicoesPossiveis = direcao.obterPosicoesPossiveis(posicao);
			Posicao posicaoSelecionada = posicao;

			for (Posicao pos : posicoesPossiveis) {
				if (this.posicaoValida(pos) && this.terreno[pos.getX()][pos.getY()] == null) {
					posicaoSelecionada = pos;
					this.setPosicao(criatura, pos);

					for (ObservadorTerreno observador : this.observadores) {
						observador.criaturaMoveu(posicao, pos);
					}
					break;
				}
			}
			this.analisarNascimentosMortes(criatura, posicaoSelecionada);
		}
	}

	@Override
	public ListaPosicao obterPosicoesRelativasPresas(AbstractCriatura criaturaReferencia) {
		Posicao posicaoReferencia = this.getPosicao(criaturaReferencia);
		return this.getPosicoesRelativas(posicaoReferencia, this.posicaoPresas);
	}

	@Override
	public ListaPosicao obterPosicoesRelativasPredadores(AbstractCriatura criaturaReferencia) {
		Posicao posicaoReferencia = this.getPosicao(criaturaReferencia);
		return this.getPosicoesRelativas(posicaoReferencia, this.posicaoPredadores);
	}

	@Override
	public void adicionarObservador(ObservadorTerreno observadorTerreno) {
		this.observadores.add(observadorTerreno);
	}

	@Override
	public Map<AbstractCriatura, Posicao> obterPosicaoCriaturas() {
		Map<AbstractCriatura, Posicao> criaturas = new HashMap<AbstractCriatura, Posicao>();
		criaturas.putAll(this.posicaoPredadores);
		criaturas.putAll(this.posicaoPresas);
		return criaturas;
	}

	private void analisarNascimentosMortes(AbstractCriatura criatura, Posicao pos) {
		AbstractCriatura[][] vizinhanca = this.obterVizinhanca(pos);

		AbstractCriatura morto = this.controleVida.analisarMortes(vizinhanca);
		if (morto != null) {
			this.criaturaMorreu(morto, this.getPosicao(morto));
		}

		if (morto != criatura) {
			AbstractCriatura recemNascido = this.controleVida.analisarNascimentos(vizinhanca, pos);
			if (recemNascido != null) {
				this.criaturaNasceu(criatura, recemNascido);
			}
		}
	}

	private void criaturaNasceu(AbstractCriatura pai, AbstractCriatura recemNascido) {
		Posicao posicaoDisponivel = this.getPosicaoDisponivel(this.getPosicao(pai));
		if (posicaoDisponivel != null) {
			recemNascido.definirTerreno(this);
			this.setPosicao(recemNascido, posicaoDisponivel);
			for (ObservadorTerreno observador : this.observadores) {
				observador.criaturaNasceu(recemNascido, posicaoDisponivel);
			}
		}
	}

	private void criaturaMorreu(AbstractCriatura criatura, Posicao posicao) {
		this.removerCriatura(criatura);
		for (ObservadorTerreno observador : this.observadores) {
			observador.criaturaMorreu(criatura, posicao);
		}
	}

	private ListaPosicao getPosicoesRelativas(Posicao posicaoReferencia, Map<AbstractCriatura, Posicao> mapaPosicoes) {
		if (posicaoReferencia != null) {
			return new ListaPosicao(mapaPosicoes.values(), posicaoReferencia);
		}
		return null;
	}

	private AbstractCriatura[][] obterVizinhanca(Posicao pos) {
		AbstractCriatura[][] vizinhanca = new AbstractCriatura[3][3];
		for (int x = 0; x < 2; x++) {
			for (int y = 0; y < 2; y++) {
				int realX = pos.getX() - 1 + x;
				int realY = pos.getY() - 1 + y;
				if (this.posicaoValida(realX, realY)) {
					vizinhanca[x][y] = this.terreno[realX][realY];
				}
			}
		}
		return vizinhanca;
	}

	private Posicao getPosicaoDisponivel(Posicao posicao) {
		for (int x = posicao.getX() - 1; x <= posicao.getX() + 1; x++) {
			for (int y = posicao.getY() - 1; y <= posicao.getY() + 1; y++) {
				if (this.posicaoValida(x, y) && this.terreno[x][y] == null) {
					return new Posicao(x, y);
				}
			}
		}
		return null;
	}

	private void disporCriaturasAleatoriamente(Set<AbstractCriatura> criaturas) {
		List<Posicao> posicoesPossiveis = new ArrayList<Posicao>(WIDTH * HEIGHT);
		for (int x = 0; x < WIDTH; x++) {
			for (int y = 0; y < HEIGHT; y++) {
				posicoesPossiveis.add(new Posicao(x, y));
			}
		}
		for (AbstractCriatura criatura : criaturas) {
			if (posicoesPossiveis.size() > 0) {
				int random = (int) (Math.random() * posicoesPossiveis.size());
				Posicao posicao = posicoesPossiveis.remove(random);
				this.setPosicao(criatura, posicao);
				criatura.definirTerreno(this);
			} else {
				break;
			}
		}
	}

	private boolean posicaoValida(Posicao pos) {
		return this.posicaoValida(pos.getX(), pos.getY());
	}

	private boolean posicaoValida(int x, int y) {
		return x >= 0 && x < WIDTH && y >= 0 && y < HEIGHT;
	}

	private void setPosicao(AbstractCriatura criatura, Posicao posicao) {
		this.removerCriatura(criatura);

		if (criatura.isPredador()) {
			this.posicaoPredadores.put(criatura, posicao);
		} else {
			this.posicaoPresas.put(criatura, posicao);
		}
		this.terreno[posicao.getX()][posicao.getY()] = criatura;
	}

	private void removerCriatura(AbstractCriatura criatura) {
		Posicao posicao = this.getPosicao(criatura);
		if (criatura.isPredador()) {
			this.posicaoPredadores.remove(criatura);
		} else {
			this.posicaoPresas.remove(criatura);
		}
		if (posicao != null) {
			this.terreno[posicao.getX()][posicao.getY()] = null;
		}
	}

	private Posicao getPosicao(AbstractCriatura criatura) {
		if (criatura.isPredador()) {
			return this.posicaoPredadores.get(criatura);
		} else {
			return this.posicaoPresas.get(criatura);
		}
	}

}
